package apibasej.db.metadata;

import java.util.ArrayList;
import java.util.LinkedHashMap;

/**
 * http://code.google.com/p/apibasej/
 * 
 * @author Julio Azevedo Lessa - apibasej@gmail.com
 * 
 */
public class Tab {

	//private TabGroup tabGroup;
	private String schema;
	private String name;
	private boolean appendSchema = false;
	
	private LinkedHashMap<String,Col<?>> cols;

	private int idTable;
	
	
	//TabGroup tabGroup, 
	public Tab(String schema, String name, int idTable, boolean appendSchema) {
		
		// para deixar mais claro erros nas instanciações das classes de constantes, ver gerador
		//if(tabGroup==null) throw new NullPointerException("TabGroup can not be null!");
		
		this.name = name;
		//this.tabGroup = tabGroup;
		this.schema = schema;
		this.appendSchema = appendSchema;
		this.idTable = idTable;
		//if(tabGroup!=null)tabGroup.getTabs().put(name,this);
	}
	
	//retorna uma nova tabela vazia (sem colunas parecida com esta)
	public Tab newEmptyTab(){
		return new Tab(schema,name,idTable,appendSchema);
	}

	public String getStringForClass(String nameClassTabGroup, String packageRoot){
		String n = getName();
		String sch = getSchema()==null?"null":"\""+getSchema()+"\"";
		
		String s =  "\r\nimport "+Tab.class.getName()+";"+
					"\r\nimport "+Col.class.getName()+";"+
					"\r\n\r\npublic class "+n+" extends Tab{\r\n" +
					
					
					// 1 - Única forma que funciona! ... ver como mudar !
					//"\r\n\tprivate "+n+"(){super("+nameClassTabGroup+".getInstance(),"+sch+",\""+n+"\","+isAppendSchema()+");}"+ 
					//"\r\n\tprivate static "+n+" instance = null;"+ 
					//"\r\n\tpublic static "+n+" getInstnce(){"+ 
					//"\r\n\t\t"+nameClassTabGroup+".getInstance();"+ // tem que chamar SEMPRE!!! sempre antes de instanciar a tab ... não sei o porque mas corrigiu alguns erros! VER mudança para bloco static ...
					//"\r\n\t\tif(instance==null) instance = new "+n+"();"+ 
					//"\r\n\t\treturn instance;"+ 
					//"\r\n\t}\r\n";
					
					// 2- PAU
					// !!! usar o método getInst() pois o field INST direto estava dando erro
					//"\r\n\tpublic static "+n+" INST = new "+n+"();\r\n"+ 
					//"\r\n\tpublic "+n+"(){super("+nameClassTabGroup+".getInstance(),"+sch+",\""+n+"\","+isAppendSchema()+");}\r\n";
				
					// 3- PAU
					// para singletons ficar igual do livro Effective Java: Programming Language Guide (TESTAR!)
					//"\r\n\tprivate static final "+n+" INSTANCE = new "+n+"();\r\n"+ 
					//"\tpublic static "+n+" getInstance(){return INSTANCE;}\r\n"+
					//"\r\n\tprivate "+n+"(){super("+nameClassTabGroup+".getInstance(),"+sch+",\""+n+"\","+isAppendSchema()+");}\r\n";
		
					//4 - PAU -  http://java.sun.com/developer/technicalArticles/Programming/singletons/
					//"\r\n\tprivate "+n+"(){super("+nameClassTabGroup+".getInstance(),"+sch+",\""+n+"\","+isAppendSchema()+");}"+ 
					//"\r\n\tprivate static "+n+" instance = null;"+ 
					//"\r\n\tpublic static synchronized "+n+" getInstance(){"+ 
					//"\r\n\t\tif(instance==null) instance = new "+n+"();"+ 
					//"\r\n\t\treturn instance;"+ 
					//"\r\n\t}\r\n";
		
					//5 -  http://java.sun.com/developer/technicalArticles/Programming/singletons/
					// as classes foram mudadas para não precisar do TabGroup, estava tendo erro ao cruzar as referências dos singletons
					"\r\n\tprivate "+n+"(){super("+sch+",\""+n+"\","+getIdTable()+","+isAppendSchema()+");}"+ 
					"\r\n\tprivate static "+n+" instance = null;"+ 
					"\r\n\tpublic static synchronized "+n+" getInstance(){"+ 
					"\r\n\t\tif(instance==null) instance = new "+n+"();"+ 
					"\r\n\t\treturn instance;"+ 
					"\r\n\t}\r\n";
		
		// adiciona primeiro as PKs, pois se tiver um coluna com auto relacionamento apontando para a pk
		// o field da pk deve ser criado antes!
		for(Col<?> c : getPks()) {
			s += c.getStringFieldForGenerator(packageRoot);
		}
		s += "\r\n";
		for(Col<?> c : getCols().values()) {
			if(!c.isPk()){
				s += c.getStringFieldForGenerator(packageRoot);
			}
		}
		
		return s+"\r\n\r\n}";
	}
	
	public ArrayList<Col<?>> getPks(){
		ArrayList<Col<?>> pks = new ArrayList<Col<?>>();
		for(Col<?> c : getCols().values()){
			if(c.isPk()) pks.add(c);
		}
		return pks;
	}
	public Col<?> getPkSingle(){
		ArrayList<Col<?>> pks = getPks();
		if(pks.size()!=1){
			throw new IllegalStateException("PK not is single! pks="+pks.size());
		}else{
			return pks.get(0);
		}
	}
	
	public ArrayList<Col<?>[]> getLinksForTab(Tab tabExt){
		ArrayList<Col<?>[]> l = getLinksForTab(tabExt, false);//primeiro tenta achar os vínculos diretos
		if(l.size()==0){
			l = getLinksForTab(tabExt, true);//se não achar, tenta os vínculos indiretos
		}
		return l;
	}
	// retorna as cols fk que apontam para as pks da tab 't'
	private ArrayList<Col<?>[]> getLinksForTab(Tab tabExt, boolean findIndirectLinks){
		ArrayList<Col<?>[]> fksToTab = new ArrayList<Col<?>[]>();
		//System.out.println("t>>"+t.getPks().size()+" - "+t.getCols().size());
		//System.out.println("t.getPks()>"+t.getPks().size()+" - "+t.getName()+"; "+getCols().values().size()+", "+getName() );
		for(Col<?> colThis : getCols().values()){
			for(Col<?> colExt : tabExt.getCols().values()){
				Col<?> colLink = colThis.getColLinkExt(colExt,findIndirectLinks);
				if(colLink!=null){
					fksToTab.add(new Col<?>[]{colThis,colLink});
				}
				/*
				//System.out.println("------->"+c.getForeignCol()+" - "+pk);
				//System.out.println("#>"+c.getTab().getName()+"."+c.getName()+".getFK="+c.getForeignCol()+" - "+pk.getTab().getName()+"."+pk.getName()+".getFK="+pk.getForeignCol());
				Col<?> fkPkExt = pk.getForeignCol();
				if( pk.equals(colThis.getForeignCol())
						|| ( !colThis.isPk() && fkPkExt!=null && fkPkExt.equals(colThis.getForeignCol())) // se aponta para uma tabela intermadiária que a pk da tab pai também aponta (como no caso org_endereco.id_ent_externa>sis_id_ctrl.id)
					) {
					fksToTab.add(colThis);
				}*/
			}
		}
		return fksToTab;
	}
	
	//se tem dois links para a mesma coluna deve escolher o correto
	
	
	
	/*
	public ArrayList<Col<?>[]> getColsFksJoin(Tab beforeTab){
		// o mais provável é que uma tab anterior tenha fk para uma pk da próxima tab (EX: NOME_PROD,MARCA), mas verificar o inverso também ...
		//System.out.println("this>"+this.getName());
		ArrayList<Col<?>[]> colsJoin = beforeTab.getLinksForTab(this);
		//* não precisa verificar novamente, pois getFksForTab foi alterado para verificar vínculos dos dois lados
		//System.out.println("beforeTab>"+beforeTab.getName());
		if(colsJoin.size()==0) {
			colsJoin = this.getFksForTab(beforeTab);
		}*
		return colsJoin;
	}*/
	
	
	//usado para retornar o nome com o schema para casos que precisem do schema no nome da tabela para os comandos SQL
	public String getNameForSql(){
		StringBuilder s = new StringBuilder();
		if(isAppendSchema()) {
			s.append(getSchema());
			s.append('.');
		}
		s.append(getName());
		return s.toString();
	}
	
	public LinkedHashMap<String,Col<?>> getCols() {
		if(cols==null) cols = new LinkedHashMap<String,Col<?>>();
		return cols;
	}

	public String getName() {
		return name;
	}
	public int getIdTable() {
		return idTable;
	}
	
	public Col<?> getCol(String colName){
		return getCols().get(colName);
	}
	
	//@Override
	//public String toString() {
	//	return getName();
	//}

	/*
	public TabGroup getTabGroup() {
		return tabGroup;
	}

	public void setTabGroup(TabGroup tabGroup) {
		this.tabGroup = tabGroup;
	}*/

	//@Override
	//public int hashCode() {
	//	return getName().hashCode();
	//}
	
	@Override
	public boolean equals(Object obj) {
		return this==obj;
		/*
		if( !(obj instanceof Tab) ) return false;
		Tab t = (Tab)obj;
		boolean ok = this.getName().equals(t.getName());
		//if(ok && c.getTab()!=null && this.getTab()!=null) ok = this.getTab().equals(c.getTab());
		return ok;
		*/
	}

	
	public String getSchema() {
		return schema;
	}
	public void setSchema(String schema) {
		this.schema = schema;
	}
	public boolean isAppendSchema() {
		return appendSchema;
	}
	public void setAppendSchema(boolean appendSchema) {
		this.appendSchema = appendSchema;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	@Override
	protected final Object clone() throws CloneNotSupportedException {
		throw new CloneNotSupportedException();
	}
	
}
